#!/usr/bin/perl

#Copyright (C) 2009  Tiziano Fogli, Simone Bernini

#This program is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public License
#as published by the Free Software Foundation; either version 2
#of the License, or (at your option) any later version.

#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.

#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# fogli@cs.unibo.it
# sbernini@cs.unibo.it

use strict;
use Switch;

use Statistics::Distributions;
use Statistics::Descriptive;

package FileScalar;

	#crea un nuovo oggetto
	#se path viene passato come undef o file non esistente: errore + exit 
	#altrimenti anche se questi vengono passati vengono sovrascritti quando possibile dalle info prese dal file
	sub new {
		my ($class, $path) = @_;
		if(not defined($path)) {
			print "errore: non puoi creare un oggetto scalar senza passare il suo path nel file system.\n";
			exit(-1);
		}
		my $fd = undef;
		open(FILE, $path) or die("cannot open file $path");	close(FILE);
		my $self = {
			_path => $path,	
            _useful => 1,
			_attributes => undef 
		};
		bless $self, $class;

		$self->parseFileName();
		
		return $self;
	}

	#ritorna tutta la path del file
	sub getPath {
		my ($self) = @_;
		return $self->{_path};
	}
    
    #controlla se il file rispetta i la hash di filtri fornita come argomento
    sub checkUtility {
        my ($self, %filters) = @_;
        
        my $bool = 1;
        foreach my $filterOnParam (keys(%filters)) {
            my $value = $self->getParameter($filterOnParam);
            if($value eq -1) { 
                die("Unknown filter."); 
            }
            if(not($filters{$filterOnParam} eq $value)) {
                $bool = 0;
            }
        }

        if($bool eq 1) {
            $self->{_useful} = 1;
        } else {
            $self->{_useful} = 0;
        }

        return $self->{_useful};
    }
	
	#ritorna il solo nome del file senza la path
	sub getFileName {
		my ($self) = @_;
		my $basename = `basename $self->{_path}`;
		chomp($basename);
		return $basename;
	}

	#restituisce il valore di un attributo contenuto nella hash _attributes
	#-1 se questo non è presente
	#se non è definito il nome del parametro: errore + morte
	sub getParameter {
		my ($self, $param) = @_;	
		defined($param) or die("parameter name expected");
		return $self->{_attributes}{$param} if defined($self->{_attributes}{$param});
		return -1;
	}
	
	#calcola la media sfruttando i risultati delle funzioni higher order fornite in ingresso
	#reg1: sub { $_[0] =~ /Router_Measured_injection_rate.\s+(0\.[0-9]*)/; return $1}, regex per il tipo di campo -> obbligatoria
	#reg2: sub { return 1 if ($_[0] =~ /.+mesh.node\[10\].+/) }, regex per selezionare il nodo su cui applicare la reg1 in caso si non voglia calcolare su tutto il file
	#faith è il grado di fedeltà dell'intervallo fiduciario: di default al 95%;
        #vproportion: fattore moltiplicativo per i valori con cui si fa match
	#restituisce un vettore di 3 elementi: intervallo fiduciario inferiore(da sottrarre alla media), media, intervallo fiduciario superiore(da addizionare alla media)
        #tutti settati a meno 1 se non son stati effettuati match
	sub calculateAverage {
        my $statCalculator = Statistics::Descriptive::Full->new();

                my %tdist = ( 20 => [ .325, .289, .277, .271, .267, .265, .263, .262, .261, .260, 
                        .260, .259, .259, .258, .258, .258, .257, .257, .257, .257, 
                        .257, .256, .256, .256, .256, .256, .256, .256, .256, .256, 
                        .253 ],
                40 => [ .727, .617, .584, .569, .559, .553, .549, .546, .543, .542,
                        .540, .539, .538, .537, .536, .535, .534, .534, .533, .533,
                        .532, .532, .532, .531, .531, .531, .531, .530, .530, .530,
                        .524 ],
                60 => [ 1.367, 1.061, .978, .941, .920, .906, .896, .889, .883, .879,
                        .876, .873, .870, .868, .866, .865, .863, .862, .861, .860,
                        .859, .858, .858, .857, .856, .856, .855, .855, .854, .854,
                        .842 ],
                80 => [ 3.078, 1.886, 1.638, 1.533, 1.476, 1.440, 1.415, 1.397, 1.383, 1.372,
                        1.363, 1.356, 1.350, 1.345, 1.341, 1.337, 1.333, 1.330, 1.328, 1.325,
                        1.323, 1.321, 1.319, 1.316, 1.316, 1.315, 1.314, 1.313, 1.310, 1.310,
                        1.282 ],
                90 => [ 6.314, 2.920, 2.353, 2.132, 2.015, 1.943, 1.895, 1.860, 1.833, 1.812,
                        1.796, 1.782, 1.771, 1.761, 1.753, 1.746, 1.740, 1.734, 1.729, 1.725,
                        1.721, 1.717, 1.714, 1.708, 1.708, 1.706, 1.703, 1.701, 1.697, 1.697,
                        1.645 ],
                95 => [ 12.706, 4.303, 3.182, 2.776, 2.571, 2.447, 2.365, 2.306, 2.262, 2.228,
                        2.201, 2.179, 2.160, 2.145, 2.131, 2.120, 2.110, 2.101, 2.093, 2.086,
                        2.080, 2.074, 2.069, 2.060, 2.060, 2.056, 2.052, 2.048, 2.042, 2.042,
                        1.960 ],
                98 => [ 31.821, 6.965, 4.541, 3.747, 3.365, 3.143, 2.998, 2.896, 2.821, 2.764,
                        2.718, 2.681, 2.650, 2.624, 2.602, 2.583, 2.567, 2.552, 2.539, 2.528,
                        2.518, 2.508, 2.500, 2.485, 2.485, 2.479, 2.473, 2.467, 2.457, 2.457,
                        2.326 ],
                99 => [ 63.657, 9.925, 5.841, 4.604, 4.032, 3.707, 3.499, 3.355, 3.250, 3.169,
                        3.106, 3.055, 3.012, 2.977, 2.947, 2.921, 2.898, 2.878, 2.861, 2.845,
                        2.831, 2.819, 2.807, 2.787, 2.787, 2.779, 2.771, 2.763, 2.750, 2.750,
                        2.576 ]  );
                my $tdist_infinity = 30;


		my ($self, $reg1, $reg2, $faith, $vproportion) = @_;
		$faith = 0.05 if(not defined($faith));
		defined($reg1) or die("manca la regex per la granularità minima.");
		#defined($reg2) or printf("FileScalar::calculateAvarage(): manca la regex per la granularità sui componenti.\n");
		
		#carico il file nel vettore lines	
		open(HANDLER, $self->getPath()) or die("file not exists");
			my @lines = <HANDLER>;
		close(HANDLER);
		
		#epurazione del vettore in base a reg2
		my @reg2ParsedLines;
		if(defined($reg2)) {
			foreach my $line (@lines) {
				@reg2ParsedLines = (@reg2ParsedLines, $line) if ($reg2->($line)); 
			}
			@lines = @reg2ParsedLines;
		}
		
		#calcolo della media sul vettore epurato o meno
		foreach my $line (@lines) {
			my $partial = ($reg1->($line));
			if(defined($partial)) {
                                if(defined($vproportion)) {
                                    $partial = $partial * $vproportion;
                                }
                $statCalculator->add_data($partial);
			}
		}
		my $counter = $statCalculator->count();
	        
        print "COUNTER ZERO\n" if ($counter == 0);        
		return (-1, -1, -1) if ($counter == 0);

		my $average = $statCalculator->mean();
		return (0 , $average, 0) if ($counter == 1);
                
		#calcolo intervalli fiduciari - da controllare
                
        # my $t = $tdist{$faith}[$tdist_infinity];
        #if(($counter - 2) < 30) {
        #    $t = $tdist{$faith}[$counter-2];
        #}
                
        my $t = Statistics::Distributions::tdistr($counter - 1, ((1 - ($faith / 100)) / 2));

		my 	$S = 0;
        #my	$sum = 0;
        #foreach my $line (@lines) {
        #	my $partial = $reg1->($line);
        #                $partial *= $vproportion if(defined($vproportion) and defined($partial));
        #	$sum += (($partial - $average) ** 2) if (defined($partial));
        #}
        #$S = sqrt($sum / $counter );
        $S = $statCalculator->standard_deviation();
		my $inf = $average - ($t * ($S / sqrt($counter - 1)));
		my $sup = $average + ($t * ($S / sqrt($counter - 1)));
		return ($inf, $average, $sup);
	}
	

	#
	#	PRIVATE FUNCTION
	#

	#parsa il nome associato al file e setta attributes nel  
	sub parseFileName {
		caller eq __PACKAGE__ or die((caller)." cannot call function from external package");
		my ($self) = @_;

        #print "*** PARSING: ".$self->getFileName()." ***\n";
		
		my $unSplittedValue = $self->getFileName();
		$unSplittedValue =~ s/.sca//;
		my @splittedValue = split(/_/, $unSplittedValue);
	
		my $paramCounter = 0;	
		foreach my $field (@splittedValue) {
            #if($field =~ /([a-zA-Z]+)(\d+)/) { //works only for decimal attribute
			if($field =~ /([a-zA-Z]+)(\d+\.*\d*)/) {
				$self->{_attributes}{$1} = $2;
                                #print "parsed attribute: ".$1." => ".$self->{_attributes}{$1}."\n";
			} else {
				my $paramName = "unnamed".$paramCounter++;
				$self->{_attributes}{$paramName} = $field;
                                #print "parsed attribute: $paramName => $self->{_attributes}{$paramName}\n";
			}
		}
	}
	
1;
